2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
26 #import "SFSignInAnalytics.h"
27 #import "SFSignInAnalytics+Internal.h"
29 #import <Analytics/SFAnalytics+Signin.h>
30 #import "SFAnalyticsDefines.h"
31 #import "SFAnalyticsSQLiteStore.h"
32 #import "SFAnalytics.h"
34 #import <os/log_private.h>
35 #import <mach/mach_time.h>
36 #import <utilities/SecFileLocations.h>
37 #import <utilities/debugging.h>
38 #import <utilities/SecCFWrappers.h>
40 //metrics database location
41 NSString* signinMetricsDatabase = @"signin_metrics";
43 //defaults write results location
44 static NSString* const SFSignInAnalyticsDumpLoggedResultsToLocation = @"/tmp/signin_results.txt";
45 static NSString* const SFSignInAnalyticsPersistedEventList = @"/tmp/signin_eventlist";
48 static NSString* const SFSignInAnalyticsAttributeRecoverableError = @"recoverableError";
49 static NSString* const SFSignInAnalyticsAttributeErrorDomain = @"errorDomain";
50 static NSString* const SFSignInAnalyticsAttributeErrorCode = @"errorCode";
51 static NSString* const SFSignInAnalyticsAttributeErrorChain = @"errorChain";
52 static NSString* const SFSignInAnalyticsAttributeParentUUID = @"parentUUID";
53 static NSString* const SFSignInAnalyticsAttributeMyUUID = @"myUUID";
54 static NSString* const SFSignInAnalyticsAttributeSignInUUID = @"signinUUID";
55 static NSString* const SFSignInAnalyticsAttributeEventName = @"eventName";
56 static NSString* const SFSignInAnalyticsAttributeSubsystemName = @"subsystemName";
57 static NSString* const SFSignInAnalyticsAttributeBuiltDependencyChains = @"dependencyChains";
59 @implementation SFSIALoggerObject
60 + (NSString*)databasePath {
61 return [SFSIALoggerObject defaultAnalyticsDatabasePath:signinMetricsDatabase];
64 + (instancetype)logger
66 return [super logger];
71 @interface SFSignInAnalytics ()
72 @property (nonatomic, copy) NSString *signin_uuid;
73 @property (nonatomic, copy) NSString *my_uuid;
74 @property (nonatomic, copy) NSString *parent_uuid;
75 @property (nonatomic, copy) NSString *category;
76 @property (nonatomic, copy) NSString *eventName;
77 @property (nonatomic, copy) NSString *persistencePath;
79 @property (nonatomic, strong) NSURL *persistedEventPlist;
80 @property (nonatomic, strong) NSMutableDictionary *eventDependencyList;
81 @property (nonatomic, strong) NSMutableArray *builtDependencyChains;
83 @property (nonatomic) BOOL canceled;
84 @property (nonatomic) BOOL stopped;
86 @property (nonatomic, strong) os_log_t logObject;
88 @property (nonatomic, strong) NSNumber *measurement;
90 @property (nonatomic, strong) dispatch_queue_t queue;
92 @property (nonatomic, strong) SFSignInAnalytics *root;
93 @property (nonatomic, strong) SFAnalyticsActivityTracker *tracker;
95 -(os_log_t) newLogForCategoryName:(NSString*) category;
96 -(os_log_t) logForCategoryName:(NSString*) category;
100 static NSMutableDictionary *logObjects;
101 static const NSString* signInLogSpace = @"com.apple.security.wiiss";
103 @implementation SFSignInAnalytics
105 + (BOOL)supportsSecureCoding {
109 -(os_log_t) logForCategoryName:(NSString*) category
111 return logObjects[category];
114 -(os_log_t) newLogForCategoryName:(NSString*) category
116 return os_log_create([signInLogSpace UTF8String], [category UTF8String]);
119 - (BOOL)writeDependencyList:(NSError**)error
121 NSError *localError = nil;
122 if (![NSPropertyListSerialization propertyList: self.root.eventDependencyList isValidForFormat: NSPropertyListXMLFormat_v1_0]){
123 os_log_error(self.logObject, "can't save PersistentState as XML");
127 NSData *data = [NSPropertyListSerialization dataWithPropertyList: self.root.eventDependencyList
128 format: NSPropertyListXMLFormat_v1_0 options: 0 error: &localError];
130 os_log_error(self.logObject, "error serializing PersistentState to xml: %@", localError);
134 BOOL writeStatus = [data writeToURL:self.root.persistedEventPlist options: NSDataWritingAtomic error: &localError];
136 os_log_error(self.logObject, "error writing PersistentState to file: %@", localError);
138 if(localError && error){
145 - (instancetype)initWithSignInUUID:(NSString *)uuid category:(NSString *)category eventName:(NSString*)eventName
153 _eventName = eventName;
154 _category = category;
158 _builtDependencyChains = [NSMutableArray array];
160 //make plist file containing uuid parent/child
161 _persistencePath = [NSString stringWithFormat:@"%@-%@.plist", SFSignInAnalyticsPersistedEventList, eventName];
162 _persistedEventPlist = [NSURL fileURLWithPath:_persistencePath isDirectory:NO];
164 _eventDependencyList = [NSMutableDictionary dictionary];
165 [_eventDependencyList setObject:[NSMutableArray array] forKey:_signin_uuid];
167 _tracker = [[SFSIALoggerObject logger] logSystemMetricsForActivityNamed:eventName withAction:nil];
170 NSError* error = nil;
171 if(![self writeDependencyList:&error]){
172 os_log(self.logObject, "attempting to write dependency list: %@", error);
175 _queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
177 static dispatch_once_t onceToken;
178 dispatch_once(&onceToken, ^{
179 logObjects = [NSMutableDictionary dictionary];
181 @synchronized(logObjects){
183 _logObject = [self logForCategoryName:category];
186 _logObject = [self newLogForCategoryName:category];
187 [logObjects setObject:_logObject forKey:category];
195 -(instancetype) initChildWithSignInUUID:(NSString*)uuid andCategory:(NSString*)category andEventName:(NSString*)eventName
203 _eventName = eventName;
204 _category = category;
210 - (void)encodeWithCoder:(NSCoder *)coder {
211 [coder encodeObject:_signin_uuid forKey:@"UUID"];
212 [coder encodeObject:_category forKey:@"category"];
213 [coder encodeObject:_parent_uuid forKey:@"parentUUID"];
214 [coder encodeObject:_my_uuid forKey:@"myUUID"];
215 [coder encodeObject:_measurement forKey:@"measurement"];
216 [coder encodeObject:_eventName forKey:@"eventName"];
219 - (nullable instancetype)initWithCoder:(NSCoder *)decoder
223 _signin_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"UUID"];
224 _category = [decoder decodeObjectOfClass:[NSString class] forKey:@"category"];
225 _parent_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"parentUUID"];
226 _my_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"myUUID"];
227 _measurement = [decoder decodeObjectOfClass:[NSString class] forKey:@"measurement"];
228 _eventName = [decoder decodeObjectOfClass:[NSString class] forKey:@"eventName"];
229 _queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
231 if(_signin_uuid == nil ||
233 _parent_uuid == nil){
234 [decoder failWithError:[NSError errorWithDomain:@"securityd" code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"Failed to decode SignInAnalytics object"}]];
241 - (SFSignInAnalytics*)newSubTaskForEvent:(NSString*)eventName
243 SFSignInAnalytics *newSubTask = [[SFSignInAnalytics alloc] initChildWithSignInUUID:self.signin_uuid andCategory:self.category andEventName:self.eventName];
245 newSubTask.my_uuid = [NSUUID UUID].UUIDString;
246 newSubTask.parent_uuid = self.my_uuid;
247 newSubTask.signin_uuid = self.signin_uuid;
249 newSubTask.category = self.category;
250 newSubTask.eventName = [eventName copy];
251 newSubTask.root = self.root;
252 newSubTask.canceled = NO;
253 newSubTask.stopped = NO;
255 newSubTask.queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
256 newSubTask.tracker = [[SFSIALoggerObject logger] logSystemMetricsForActivityNamed:eventName withAction:nil];
257 [newSubTask.tracker start];
259 @synchronized(_eventDependencyList){
260 NSMutableArray *parentEntry = [newSubTask.root.eventDependencyList objectForKey:newSubTask.parent_uuid];
262 //add new subtask entry to parent event's list
263 [parentEntry addObject:newSubTask.my_uuid];
264 [newSubTask.root.eventDependencyList setObject:parentEntry forKey:newSubTask.parent_uuid];
266 //create new array list for this new subtask incase it has subtasks
267 [newSubTask.root.eventDependencyList setObject:[NSMutableArray array] forKey:newSubTask.my_uuid];
268 NSError* error = nil;
269 if(![newSubTask writeDependencyList:&error]){
270 os_log(self.logObject, "attempting to write dependency list: %@", error);
278 - (void)logRecoverableError:(NSError*)error
282 os_log_error(self.logObject, "attempting to log a nil error for event:%@", self.eventName);
286 os_log_error(self.logObject, "%@", error);
288 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
290 [eventAttributes setValuesForKeysWithDictionary:@{
291 SFSignInAnalyticsAttributeRecoverableError : @(YES),
292 SFSignInAnalyticsAttributeErrorDomain : error.domain,
293 SFSignInAnalyticsAttributeErrorCode : @(error.code),
294 SFSignInAnalyticsAttributeMyUUID : self.my_uuid,
295 SFSignInAnalyticsAttributeParentUUID : self.parent_uuid,
296 SFSignInAnalyticsAttributeSignInUUID : self.signin_uuid,
297 SFSignInAnalyticsAttributeEventName : self.eventName,
298 SFSignInAnalyticsAttributeSubsystemName : self.category
301 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:self.eventName withAttributes:eventAttributes];
305 - (void)logUnrecoverableError:(NSError*)error
308 os_log_error(self.logObject, "attempting to log a nil error for event:%@", self.eventName);
312 os_log_error(self.logObject, "%@", error);
314 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
316 [eventAttributes setValuesForKeysWithDictionary:@{
317 SFSignInAnalyticsAttributeRecoverableError : @(NO),
318 SFSignInAnalyticsAttributeErrorDomain : error.domain,
319 SFSignInAnalyticsAttributeErrorCode : @(error.code),
320 SFSignInAnalyticsAttributeMyUUID : self.my_uuid,
321 SFSignInAnalyticsAttributeParentUUID : self.parent_uuid,
322 SFSignInAnalyticsAttributeSignInUUID : self.signin_uuid,
323 SFSignInAnalyticsAttributeEventName : self.eventName,
324 SFSignInAnalyticsAttributeSubsystemName : self.category
327 [[SFSIALoggerObject logger] logHardFailureForEventNamed:self.eventName withAttributes:eventAttributes];
332 dispatch_sync(self.queue, ^{
333 [self.tracker cancel];
335 os_log(self.logObject, "canceled timer for %@", self.eventName);
339 - (void)stopWithAttributes:(NSDictionary<NSString*, id>*)attributes
341 dispatch_sync(self.queue, ^{
343 if(self.canceled || self.stopped){
351 NSMutableDictionary *mutableAttributes = nil;
354 mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
357 mutableAttributes = [NSMutableDictionary dictionary];
359 mutableAttributes[SFSignInAnalyticsAttributeMyUUID] = self.my_uuid;
360 mutableAttributes[SFSignInAnalyticsAttributeParentUUID] = self.parent_uuid;
361 mutableAttributes[SFSignInAnalyticsAttributeSignInUUID] = self.signin_uuid;
362 mutableAttributes[SFSignInAnalyticsAttributeEventName] = self.eventName;
363 mutableAttributes[SFSignInAnalyticsAttributeSubsystemName] = self.category;
365 [mutableAttributes enumerateKeysAndObjectsUsingBlock:^(NSString* key, id obj, BOOL * stop) {
366 os_log(self.logObject, "event: %@, %@ : %@", self.eventName, key, obj);
369 [[SFSIALoggerObject logger] logSuccessForEventNamed:self.eventName];
370 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:self.eventName withAttributes:mutableAttributes];
374 -(BOOL) writeResultsToTmp {
376 bool shouldWriteResultsToTemp = NO;
377 CFBooleanRef toTmp = (CFBooleanRef)CFPreferencesCopyValue(CFSTR("DumpResultsToTemp"),
378 CFSTR("com.apple.security"),
379 kCFPreferencesAnyUser, kCFPreferencesAnyHost);
380 if(toTmp && CFGetTypeID(toTmp) == CFBooleanGetTypeID()){
381 if(toTmp == kCFBooleanFalse){
382 os_log(self.logObject, "writing results to splunk");
383 shouldWriteResultsToTemp = NO;
385 if(toTmp == kCFBooleanTrue){
386 os_log(self.logObject, "writing results to /tmp");
387 shouldWriteResultsToTemp = YES;
391 CFReleaseNull(toTmp);
392 return shouldWriteResultsToTemp;
395 - (void)processEventChainForUUID:(NSString*)uuid dependencyChain:(NSString*)dependencyChain
397 NSString* newChain = dependencyChain;
399 NSArray* children = [self.root.eventDependencyList objectForKey:uuid];
400 for (NSString* child in children) {
401 newChain = [NSString stringWithFormat:@"%@, %@", dependencyChain, child];
402 [self processEventChainForUUID:child dependencyChain:newChain];
404 if([children count] == 0){
405 [self.root.builtDependencyChains addObject:newChain];
406 os_log(self.logObject, "current dependency chain list: %@", newChain);
410 - (void)signInCompleted
413 os_log(self.logObject, "sign in complete");
414 NSError* error = nil;
416 //create dependency chains and log them
417 [self processEventChainForUUID:self.root.my_uuid dependencyChain:self.root.signin_uuid];
419 if([self.root.builtDependencyChains count] > 0){
420 NSDictionary* eventAttributes = @{SFSignInAnalyticsAttributeBuiltDependencyChains : self.root.builtDependencyChains};
421 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:SFSignInAnalyticsAttributeBuiltDependencyChains withAttributes:eventAttributes];
424 BOOL writingToTmp = [self writeResultsToTmp];
425 if(writingToTmp){ //writing sign in analytics to /tmp
426 os_log(self.logObject, "logging to /tmp");
428 NSData* eventData = [NSKeyedArchiver archivedDataWithRootObject:[[SFSIALoggerObject logger].database allEvents] requiringSecureCoding:YES error:&error];
430 [eventData writeToFile:SFSignInAnalyticsDumpLoggedResultsToLocation options:0 error:&error];
433 os_log_error(self.logObject, "error writing to file [%@], error:%@", SFSignInAnalyticsDumpLoggedResultsToLocation, error);
435 os_log(self.logObject, "successfully wrote sign in analytics to:%@", SFSignInAnalyticsDumpLoggedResultsToLocation);
438 os_log_error(self.logObject, "collected no data");
440 }else{ //writing to splunk
441 os_log(self.logObject, "logging to splunk");
444 //remove dependency list
445 BOOL removedPersistedDependencyList = [[NSFileManager defaultManager] removeItemAtPath:self.persistencePath error:&error];
447 if(!removedPersistedDependencyList || error){
448 os_log(self.logObject, "encountered error when attempting to remove persisted event list: %@", error);